
/*
 * Brief:       自动生成Makefile
 *              基本原理是在顶级源码目录下扫描各个C/CPP文件，并对其依赖关系进行
 *              分析，根据结果，自动生成Makefile
 *
 * File:        autogen.js
 * Author:      huangsanyi <huangsanyi@gmail.com>
 *
 */

/*
 * History:
 * 1,2012-10-10,huangsanyi,created.
 */

var _FS = require("fs");
var _PATH = require("path");
var _UTIL = require("util");
var _VM = require("vm");
var _CHILD = require("child_process");

Function.prototype.bind = function(obj)
{
	var method = this;
	return function(){ return method.apply(obj, arguments); };
};

/************************************************************/

/* 文件过滤器 */
function FileFilter()
{
    /* 排除项和包含项 */
    this.exclusives = [];
    this.inclusives = [];
}

/* 设定排除项和包含项，用RegExp实现，参数传入RegExp的文本串或其数组 */
FileFilter.prototype.SetExclusives = function()
{
    if(arguments.length > 1)
    {
        var i = 0;
        for(; i < arguments.length; ++i)
        {
            var re = new RegExp(arguments[i], "i");
            re.compile(arguments[i], "i");
            this.exclusives.push(re);
        }
    }
    else if(arguments.length === 1)
    {
        var re = new RegExp(arguments[0], "i");
        re.compile(arguments[0], "i");
        this.exclusives.push(re);
    }

    /* console.log(this.exclusives); */
}

FileFilter.prototype.SetInclusives = function()
{
    if(arguments.length > 1)
    {
        var i = 0;
        for(; i < arguments.length; ++i)
        {
            var re = new RegExp(arguments[i], "i");
            re.compile(arguments[i], "i");
            this.inclusives.push(re);
        }
    }
    else if(arguments.length === 1)
    {
        var re = new RegExp(arguments[0], "i");
        re.compile(arguments[0], "i");
        this.inclusives.push(re);
    }

    /* console.log(this.inclusives); */
}

/* 对给定的文件名进行匹配检查，看是否是排除项哈市包含项 */
FileFilter.prototype.MatchExclusive = function(file)
{
    var i = 0;
    for(; i < this.exclusives.length; ++i)
    {
        var match = file.match(this.exclusives[i]);
        if(null != match)
        {
            return true;
        }
    }

    return false;
}

FileFilter.prototype.MatchInclusive = function(file)
{
    var i = 0;
    for(; i < this.inclusives.length; ++i)
    {
        var inclusive = this.inclusives[i];
        var match = file.match(inclusive);
        if(null != match)
        {
            return true;
        }
    }

    return false;
}

/************************************************************/

/* 扫描器，扫描到的文件经filter做过滤处理之后交给handler处理 */
function FolderScanner(handler, filter)
{
    this.handler = handler;
    this.filter = filter;
}

FolderScanner.prototype.EntriesCallback = function(folder, entries)
{
    var i = 0;
    for(; i < entries.length; ++i)
    {
        var path = _PATH.join(folder, entries[i]);
        try
        {
            var stat = _FS.statSync(path);
            if(stat.isFile())
            {
                if(this.filter.MatchExclusive(path))
                {
                    continue;
                }

                if(this.filter.MatchInclusive(path))
                {
                    this.handler.Handle(path.replace(/\\/g, "/"));
                }
            }
            else if(stat.isDirectory())
            {
                /* 暂时用递归的方法实现子目录扫描
                   稍后考虑使用消息通知机制
                 */
                arguments.callee.bind(this)(path, _FS.readdirSync(path));
            }
            else
            {
                console.error("Unkown type of entry: %s.", path);
            }
        }
        catch (ex)
        {
            console.error("Error: %s", _UTIL.inspect(ex));
        }
    }
}

/* 深度扫描一个给定的目录，将其中所含的文件交给所注册的handler处理 */
FolderScanner.prototype.Scan = function(path)
{
    _FS.readdir(path, function(err, entries){
        if(err)
        {
            console.error("Path: %s, Error:\n", path);
            console.error(err);

            return;
        }

        this.EntriesCallback(path, entries);
    }.bind(this));
}

/************************************************************/

/*
    文件处理器
    对给定的文件进行分析，获取其所依赖的文件
 */
function MakefileGenerator(param)
{
    if(!(param instanceof Object))
    {
        param = {};
    }

    if(param.CC === undefined)
    {
        param.CC = "gcc";
    }
    if(param.CXX === undefined)
    {
        param.CXX = "g++";
    }
    if(param.LD === undefined)
    {
        param.LD = "gcc";
    }
    if(param.RM === undefined)
    {
        param.RM = "rm";
    }
    if(param.CC_FLAGS === undefined)
    {
        param.CC_FLAGS = "";
    }
    if(param.CXX_FLAGS === undefined)
    {
        param.CXX_FLAGS = "";
    }
    if(param.LD_FLAGS === undefined)
    {
        param.LD_FLAGS = "";
    }
    if(param.INC_PATHS === undefined)
    {
        param.INC_PATHS = [];
    }
    if(param.LIB_PATHS === undefined)
    {
        param.LIB_PATHS = [];
    }
    if(param.LIBS === undefined)
    {
        param.LIBS = [];
    }

    if(param.BIN === undefined)
    {
        param.BIN = "a.exe";
    }

    this.param = {
        CC: param.CC,
        CXX: param.CXX,
        LD: param.LD,
        RM: param.RM,
        CC_FLAGS: param.CC_FLAGS,        
        CXX_FLAGS: param.CXX_FLAGS,
        LD_FLAGS: param.LD_FLAGS,
        INC_PATHS: param.INC_PATHS,
        LIB_PATHS: param.LIB_PATHS,
        LIBS: param.LIBS,
        BIN: param.BIN
    };

    //console.log(this.param);
}

/* 写入Makefile头部基本信息 */
MakefileGenerator.prototype.Init = function()
{
    this.WriteMakefile("CC := " + this.param.CC + "\n");
    this.WriteMakefile("CXX := " + this.param.CXX + "\n");
    this.WriteMakefile("LD := " + this.param.LD + "\n");
    this.WriteMakefile("RM := " + this.param.RM + "\n");
    this.WriteMakefile("\n");

    var i;
    var cc_flags = this.param.CC_FLAGS;
    var cxx_flags = this.param.CXX_FLAGS;
    for(i = 0; i < this.param.INC_PATHS.length; ++i)
    {
        cc_flags += " -I" + this.param.INC_PATHS[i];
        cxx_flags += " -I" + this.param.INC_PATHS[i];
    }

    //console.log(cc_flags);
    //console.log(cxx_flags);

    this.WriteMakefile("CC_FLAGS := " + cc_flags + "\n");
    this.WriteMakefile("CXX_FLAGS := " + cxx_flags + "\n");

    var ld_flags = this.param.LD_FLAGS;
    for(i = 0; i < this.param.LIB_PATHS.length; ++i)
    {
        ld_flags += " -L"+this.param.LIB_PATHS[i];
    }
    for(i = 0; i < this.param.LIBS.length; ++i)
    {
        ld_flags += " -l" + this.param.LIBS[i];
    }
    this.WriteMakefile("LD_FLAGS :=" + ld_flags + "\n");
    this.WriteMakefile("\n");
    this.WriteMakefile("BIN := " + this.param.BIN + "\n");
    this.WriteMakefile("\n");
    this.WriteMakefile("OBJS := ");
}

/* 完成Makefile的写入 */
MakefileGenerator.prototype.Fini = function()
{
    this.WriteMakefile("\n\n");
    this.WriteMakefile(".PHONY: all clean\n");
    this.WriteMakefile("\n");
    this.WriteMakefile("all: $(BIN)\n");
    this.WriteMakefile("\n");
    this.WriteMakefile("$(BIN): $(OBJS)\n");
    this.WriteMakefile("\t$(LD) $^ -o $@ $(LD_FLAGS)\n");
    this.WriteMakefile("\n");
    this.WriteMakefile("include Makefile.dep\n");
    this.WriteMakefile("\n");
    this.WriteMakefile("clean:\n");
    this.WriteMakefile("\t$(RM) $(OBJS) $(BIN)\n");
}

/* 接收依赖关系，将其写入编译依赖关系文件Makefile.dep */
MakefileGenerator.prototype.GccMMOut = function(file, output)
{
    var make_cmd;
    if(null != file.match(/.c$/i))
    {
        make_cmd = _PATH.dirname(file) + "/" + output + "\t$(CC) -c $< -o $@ $(CC_FLAGS)\r\n";
    }
    else if(null != file.match(/.cpp$/i))
    {
        make_cmd = _PATH.dirname(file) + "/" + output + "\t$(CC) -c $< -o $@ $(CXX_FLAGS)\r\n";
    }
    else
    {
        make_cmd = _PATH.dirname(file) + "/" + output + "\t$(CC) -c $< -o $@\r\n";
    }

    this.WriteMakefile(" \\\n" + file.replace(/\.c$|\.cpp$/, ".o"));
    //console.log(make_cmd);
    _FS.appendFileSync("Makefile.dep", make_cmd, "ascii");
}

MakefileGenerator.prototype.WriteMakefile = function(line)
{
    _FS.appendFileSync("Makefile", line, "ascii");
}

/* 处理扫描器提供的扫描到的文件
   使用GCC分析文件依赖关系，分级结果由GccMMOut接收 
*/
MakefileGenerator.prototype.Handle = function(file)
{
    /* 1,根据提供的INC_PATHS构建适当的GCC处理命令 */
    var gcc_cmd = "gcc -MM";
    var i = 0;
    for(; i < this.param.INC_PATHS.length; ++i)
    {
        var path = this.param.INC_PATHS[i];

        gcc_cmd += " -I" + path;
    }
    gcc_cmd += " " + file;
    
    console.log(gcc_cmd);

    /* 2,调用gcc分析依赖关系 */
    _CHILD.exec(gcc_cmd, {encoding: "ascii"}, function(err, stdout, stderr){
        if(err)
        {
            console.error(err);
            console.error(stderr);
        }
        else
        {
            this.GccMMOut(file, stdout);
        }
    }.bind(this));
}

/************************************************************/

function main()
{
    var _CONF = "am.json";

    /* 读取配置文件 */
    var param = {};
    if(_FS.existsSync(_CONF))
    {
        var content = _FS.readFileSync(_CONF, "ascii");
        var o = _VM.runInThisContext("(" + content + ")");
        if(o instanceof Object)
        {
            for(var k in o)
            {
                param[k] = o[k];
            }
        }
    }

    var generator = new MakefileGenerator(param);
    generator.Init();

    //return;

    var filter = new FileFilter();
    filter.SetInclusives("\\.c$", "\\.cpp$");    
    filter.SetExclusives("tp_source_timer.c");

    var Scanner = new FolderScanner(generator, filter);

    if(param.SRC_FOLDERS instanceof Array)
    {    
        for(var i = 0; i < param.SRC_FOLDERS.length; ++i)
        {
            Scanner.Scan(param.SRC_FOLDERS[i]);
        }
    }

    process.on('exit', function () {
        generator.Fini();
    });
}

main(); 
